﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.Text;
using System.Threading.Tasks;
using WJP.Configuration;
using WJP.Logging;

namespace WJP.DataPortal.WCF
{
    /// <summary>
    /// 实现一个数据门户代理，通过使用WCF来将数据门户调用中继到远程应用服务器
    /// </summary>
    public class ClientProxy : IDataPortalProxy, IDataPortalServer
    {
        private static string _EndpointGroup = "default";
        private static ClientConfigElement Config = PlatformEnvironment.Configuration.Client;
        private int maxRetries = 3;
        private int retries = 0;
        private ChannelFactory<IWcfPortal> channel;
        private static Host currentHost;

        /// <summary>
        /// 终结点组
        /// </summary>
        public static string EndpointGroup
        {
            get
            {
                return _EndpointGroup;
            }
            set
            {
                currentHost = null;
                _EndpointGroup = value;
            }
        }

        /// <summary>
        /// 创建通道工厂
        /// 使用客户端的Client-ServiceEndpoint属性，是一个客户端终结点名称
        /// </summary>
        /// <returns></returns>
        private ChannelFactory<IWcfPortal> GetChannelFactory()
        {
            if (channel == null)
                channel = new ChannelFactory<IWcfPortal>(Config.ServiceEndpoint);
            return channel;
        }

        /// <summary>
        /// 获取当前Host信息
        /// </summary>
        /// <returns></returns>
        private Host GetHost()
        {
            if (currentHost == null)
                currentHost = Host.GetHost();
            return currentHost;
        }

        /// <summary>
        /// 返回数据结果
        /// </summary>
        /// <param name="response"></param>
        /// <returns></returns>
        private static DataPortalResult ReturnResult(WcfResponse response)
        {
            object result = response.Result;
            if (result is Exception)
                throw new DataPortalException("连接服务器时，发生异常，请检查 InnerException。", result as Exception);
            return (DataPortalResult)result;
        }

        /// <summary>
        /// 数据门户查询
        /// </summary>
        /// <param name="objectType"></param>
        /// <param name="criteria"></param>
        /// <param name="context"></param>
        /// <returns></returns>
        public DataPortalResult Fetch(Type objectType, object criteria, DataPortalContext context)
        {
            ChannelFactory<IWcfPortal> channelFactory = GetChannelFactory();
            string address = GetHost().Address;
            EndpointAddress endpointAddress = new EndpointAddress(address + Config.ServiceAddress);
            IWcfPortal channel = channelFactory.CreateChannel(endpointAddress);
            try
            {
                FetchRequest request = new FetchRequest()
                {
                    ObjectType = objectType,
                    Criteria = criteria,
                    Context = context
                };
                WcfResponse response = channel.Fetch(request);
                if (channelFactory != null)
                    channelFactory.Close();
                return ReturnResult(response);
            }
            catch (Exception ex)
            {
                Host.Faild(currentHost.Address);
                currentHost = null;
                if(ex is FaultException && retries < maxRetries)
                {
                    retries++;
                    LogManager.Logger.Error("FetchFaild > Host：{0},Retries：{1}".FormatArgs(address, retries), ex);
                    return Fetch(objectType, criteria, context);
                }
                channelFactory.Abort();
                throw ex;
            }
            finally
            {
                retries = 0;
                Host.FailAddress.Clear();
            }
        }

        /// <summary>
        /// 数据门户更新
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="context"></param>
        /// <returns></returns>
        public DataPortalResult Update(object obj, DataPortalContext context)
        {
            // TODO:Update
            throw new NotImplementedException();
        }

        private class Host
        {
            public string Address { get; set; }

            public string Group { get; set; }

            public int Failed { get; set; }

            public static List<Host> Hosts { get; } = new List<Host>();

            public static List<string> FailAddress { get; } = new List<string>();

            static Host()
            {
                foreach(AddressElement host in Config.HostCollection)
                {
                    Hosts.Add(new Host()
                    {
                        Address = host.BaseAddress,
                        Group = host.Group,
                    });
                }
            }

            /// <summary>
            /// 从当前终结点组中获取Host信息
            /// 先找失败次数最小的Host，失败后找下一个Host
            /// </summary>
            /// <returns></returns>
            public static Host GetHost()
            {
                Host host = Hosts.Where(p => p.Group.CIEquals(EndpointGroup) && !FailAddress.Contains(p.Address)).OrderBy(p => p.Failed).FirstOrDefault();
                if (host == null)
                    host = Hosts.Where(p => p.Group.CIEquals(EndpointGroup)).OrderBy(p => p.Failed).FirstOrDefault();
                if(host == null)
                    throw new AppException("客户端找不到可用的Endpoint地址,Group=" + EndpointGroup);

                return host;
            }

            /// <summary>
            /// 失败
            /// </summary>
            /// <param name="address">地址</param>
            public static void Faild(string address)
            {
                Host host = Hosts.Where(p =>
                {
                    if (p.Group.CIEquals(EndpointGroup))
                        return p.Address.CIEquals(address);
                    return false;
                }).FirstOrDefault();
                if (host == null)
                    return;
                ++host.Failed;
                if(!FailAddress.Contains(address))
                    FailAddress.Add(address);
            }
        }
    }
}
